﻿using System.Collections;
using System.Diagnostics;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService;

/// <summary>
///     存储错误信息时，使用一个链表记录单个错误链条。在链条结束时，将反向合并到最后一个错误信息。
/// </summary>
public class DiagnosticChain : IEnumerable<DiagnosticInfo>
{
    private DiagnosticNode? _startNode;


    /// <summary>
    ///     如果没有保存任何错误信息，返回<see langword="true" />。
    /// </summary>
    public bool IsEmpty => _startNode == null;

    public IEnumerator<DiagnosticInfo> GetEnumerator()
    {
        return _startNode == null
            ? Enumerable.Empty<DiagnosticInfo>().GetEnumerator()
            : new DiagnosticNodeEnumerator(_startNode);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    /// <summary>
    ///     报告一个错误信息到当前链表的末尾。
    /// </summary>
    /// <param name="descriptors">错误模板信息</param>
    /// <param name="span">错误对应的文本区间</param>
    /// <param name="args">模板信息格式化参数列表</param>
    public void Raise(DiagnosticDescriptor descriptors, in TextSpan span, params object?[] args)
    {
        DiagnosticNode node = new(new DiagnosticInfo(
            SyntaxLocation.Create(span.Uri, span.Start, span.Width), descriptors, args));
        if (_startNode == null)
        {
            _startNode = node;
        }
        else
        {
            node.Prev = _startNode.Prev;
            _startNode.Prev.Next = node;
            _startNode.Prev = node;
            node.Next = _startNode;
        }
    }


    /// <summary>
    ///     报告一个错误信息到当前链表的末尾。
    /// </summary>
    /// <param name="descriptor">错误模板信息</param>
    /// <param name="node">发生错误的文本节点</param>
    /// <param name="args">模板信息格式化参数列表</param>
    public void Raise(DiagnosticDescriptor descriptor, ITextNode node, params object?[] args)
    {
        Raise(descriptor, node.Span, args);
    }


    /// <summary>
    ///     将错误链合并成一个错误信息并返回。
    /// </summary>
    public DiagnosticInfo GetDiagnostic()
    {
        Debug.Assert(_startNode != null);
        DiagnosticNode node = _startNode.Prev;
        DiagnosticInfo info = node.Diagnostic;

        while (node.Prev != _startNode)
        {
            node = node.Prev;
            info.Related.Add(node.Diagnostic);
        }

        return info;
    }


    private class DiagnosticNode : IEnumerable<DiagnosticInfo>
    {
        public DiagnosticNode(DiagnosticInfo diagnostic)
        {
            Diagnostic = diagnostic;
            Prev = this;
            Next = this;
        }

        public DiagnosticInfo Diagnostic { get; }

        public DiagnosticNode Prev { get; set; }

        public DiagnosticNode Next { get; set; }


        public IEnumerator<DiagnosticInfo> GetEnumerator()
        {
            return new DiagnosticNodeEnumerator(this);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


    private struct DiagnosticNodeEnumerator(DiagnosticNode node) : IEnumerator<DiagnosticInfo>
    {
        private readonly DiagnosticNode _node = node;
        private DiagnosticNode _current = node;
        private bool _firstNode = true;

        public bool MoveNext()
        {
            if (_firstNode)
            {
                _firstNode = false;
                return true;
            }

            if (_current.Next == _node)
            {
                return false;
            }

            _current = _current.Next;
            return true;
        }

        public void Reset()
        {
            _firstNode = true;
            _current = _node;
        }

        public DiagnosticInfo Current => _current.Diagnostic;

        object IEnumerator.Current => _current.Diagnostic;

        public void Dispose()
        {
        }
    }
}